﻿namespace Guia.Locator
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Windows.Automation;
    using Core;

    /// <summary>
    /// This class represents a SearchCondition. It is used to search for UIElements
    /// </summary>
    public class SearchCondition
    {
        /// <summary>
        /// The list of conditions
        /// </summary>
        private readonly List<Condition> conditions;

        /// <summary>
        /// The scope fo the search
        /// </summary>
        private TreeScope scope;

        /// <summary>
        /// Initializes a new instance of the <see cref="SearchCondition"/> class.
        /// </summary>
        /// <param name="condition">
        /// The condition.
        /// </param>
        private SearchCondition(Condition condition)
        {
            this.scope = TreeScope.Descendants;
            this.conditions = new List<Condition> { condition };
        }

        /// <summary>
        /// Gets or sets Scope.
        /// </summary>
        public TreeScope Scope
        {
            get { return this.scope; }
            set { this.scope = value; }
        }

        /// <summary>
        /// Creates a SearchCondition by the AutomationId
        /// </summary>
        /// <param name="automationId">
        /// The automation id to search for.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if automationId is null.</exception>
        public static SearchCondition ByAutomationId(string automationId)
        {
            Contract.Requires<ArgumentNullException>(automationId != null);
            return ByAutomationProperty(AutomationElement.AutomationIdProperty, automationId);
        }

        /// <summary>
        /// Creates a SearchCondition by the ClassName
        /// </summary>
        /// <param name="className">
        /// The class name to search for.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if className is null.</exception>
        public static SearchCondition ByClassName(string className)
        {
            Contract.Requires<ArgumentNullException>(className != null);
            return ByAutomationProperty(AutomationElement.ClassNameProperty, className);
        }

        /// <summary>
        /// Creates a SearchCondition by the Name
        /// </summary>
        /// <param name="name">
        /// The name to search for.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if name is null.</exception>
        public static SearchCondition ByName(string name)
        {
            Contract.Requires<ArgumentNullException>(name != null);
            return ByAutomationProperty(AutomationElement.NameProperty, name);
        }

        /// <summary>
        /// Creates a SearchCondition by the ControlType
        /// </summary>
        /// <param name="controlType">
        /// The control type to search for .
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if controlType is null.</exception>
        public static SearchCondition ByControlType(ControlType controlType)
        {
            Contract.Requires<ArgumentNullException>(controlType != null);
            return ByAutomationProperty(AutomationElement.ControlTypeProperty, controlType);
        }

        /// <summary>
        /// Creates a SearchCondition by the ProcessId
        /// </summary>
        /// <param name="processId">
        /// The process id to search for .
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        public static SearchCondition ByProcessId(int processId)
        {
            return ByAutomationProperty(AutomationElement.ProcessIdProperty, processId);
        }

        /// <summary>
        /// Creates a SearchCondition for an AutomationProperty
        /// </summary>
        /// <param name="property">
        /// The property.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if property or value is null.</exception>
        public static SearchCondition ByAutomationProperty(AutomationProperty property, object value)
        {
            Contract.Requires<ArgumentNullException>(property != null);
            Contract.Requires<ArgumentNullException>(value != null);
            Condition condition = new PropertyCondition(property, value);
            return new SearchCondition(condition);
        }

        /// <summary>
        /// Creates a SearchCondition from an AutomationCondition
        /// </summary>
        /// <param name="condition">
        /// The condition.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if condition is null.</exception>
        public static SearchCondition FromAutomationCondition(Condition condition)
        {
            Contract.Requires<ArgumentNullException>(condition != null);
            return new SearchCondition(condition);
        }

        /// <summary>
        /// Adds a new Condition (and) to the SearchCondition and returns the new one
        /// </summary>
        /// <param name="searchCondition">
        /// The search condition to add.
        /// </param>
        /// <returns>
        /// The SearchCondition
        /// </returns>
        /// <exception cref="ArgumentNullException">This exception is thrown if searchCondition is null.</exception>
        public SearchCondition And(SearchCondition searchCondition)
        {
            Contract.Requires<ArgumentNullException>(searchCondition != null);
            Condition condition = searchCondition.CreateCondition();
            this.conditions.Add(condition);
            return this;
        }

        /// <summary>
        /// Creates the AutomationCondition form the SearchConditions
        /// </summary>
        /// <returns>
        /// The created AutomationCondition
        /// </returns>
        public Condition CreateCondition()
        {
            Contract.Requires<OperationNotSupportedException>(this.conditions.Count >= 1, "Couldn't create condition because there were no SearchConditions");
            Condition condition;
            if (this.conditions.Count > 1)
            {
                condition = new AndCondition(this.conditions.ToArray());
            }
            else 
            {
                condition = this.conditions[0];
            }

            return condition;
        }
    }
}